package cn.juque.jdfsystem.api;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.Sign;
import cn.hutool.crypto.asymmetric.SignAlgorithm;
import cn.hutool.crypto.digest.Digester;
import cn.hutool.crypto.digest.HMac;
import cn.hutool.crypto.digest.HmacAlgorithm;
import cn.juque.common.base.BaseRequestVO;
import cn.juque.common.base.BaseResponseVO;
import cn.juque.common.constants.MessageEnum;
import cn.juque.common.constants.NumberConstant;
import cn.juque.common.constants.StringConstants;
import cn.juque.common.exception.AppException;
import cn.juque.common.fastjson.abstracts.AbstractTypeReference;
import cn.juque.common.utils.HttpClientUtil;
import cn.juque.common.utils.dto.GetDTO;
import cn.juque.common.utils.dto.PostDTO;
import cn.juque.common.utils.dto.UploadDTO;
import cn.juque.jdfsystem.dto.FileInfoDetailDTO;
import cn.juque.jdfsystem.dto.FileTrustParamDTO;
import cn.juque.jdfsystem.dto.FileUploadParamDTO;
import cn.juque.jdfsystem.enums.FileInfoScopeEnum;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * @author juque
 * @version 1.0.0
 * <ul>
 *     <li>FileInfoClient</li>
 * </ul>
 * @date 2023-01-13 14:20:20
 **/
@Slf4j
public class FileInfoClient {

    private static final String SIGNATURE = "signature";

    private static final String H_MAC_KEY = "jdf-system-2023";

    private static final String HTTP_SUFFIX = "://";

    private static final HMac H_MAC = new HMac(HmacAlgorithm.HmacMD5, H_MAC_KEY.getBytes(StandardCharsets.UTF_8));

    private static final Sign H_SING = SecureUtil.sign(SignAlgorithm.MD5withRSA);

    private static final Integer NINETY_NINE = 99;

    /**
     * 请求协议
     */
    private final String protocol;

    /**
     * 节点IP列表
     */
    private final List<String> ipList;

    /**
     * 平台ID
     */
    private final String platformId;

    /**
     * 凭据
     */
    private final String secret;

    /**
     * 访问ip/域名
     */
    private final List<String> viewUrlList;

    public FileInfoClient(String protocol, List<String> ipList, String platformId, String secret, List<String> viewUrlList) {
        Assert.notEmpty(protocol);
        Assert.notEmpty(ipList);
        Assert.notEmpty(platformId);
        Assert.notEmpty(secret);
        Assert.notEmpty(viewUrlList);
        this.protocol = protocol;
        this.ipList = ipList;
        this.platformId = platformId;
        this.secret = secret;
        this.viewUrlList = viewUrlList;
    }

    /**
     * 确认文件状态
     *
     * @param validFileIdList   可见的文件列表
     * @param inValidFileIdList 不可见的文件列表
     * @return true=成功；false=失败
     */
    public Boolean trustFile(List<String> validFileIdList, List<String> inValidFileIdList) {
        long startTime = System.currentTimeMillis();
        FileTrustParamDTO paramDTO = new FileTrustParamDTO();
        paramDTO.setValidFileIdList(validFileIdList);
        paramDTO.setInValidFileIdList(inValidFileIdList);
        BaseRequestVO<FileTrustParamDTO> requestVO = new BaseRequestVO<>();
        requestVO.setParam(paramDTO);
        Map<String, String> headers = this.createHeaders(null);
        PostDTO postDTO = new PostDTO(createTrustUrl(), headers, requestVO, null);
        BaseResponseVO<Boolean> responseVO = HttpClientUtil.post(postDTO, new AbstractTypeReference<BaseResponseVO<Boolean>>() {
        });
        log.info("FileInfoClient.trustFile cost {}ms", System.currentTimeMillis() - startTime);
        return MessageEnum.OK.getCode().equals(responseVO.getCode());
    }

    /**
     * 上传文件
     *
     * @param paramDTO 文件信息
     * @return 文件ID
     */
    public String upload(FileUploadParamDTO paramDTO) {
        Assert.notNull(paramDTO);
        Assert.notEmpty(paramDTO.getFilePath());
        Assert.isTrue(FileUtil.exist(paramDTO.getFilePath()), "{}文件不存在", paramDTO.getFilePath());

        long startTime = System.currentTimeMillis();
        UploadDTO uploadDTO = new UploadDTO();
        uploadDTO.setUrl(this.createUploadUrl());
        Map<String, String> headerMap = new HashMap<>(2);
        headerMap.put("fileScope", Objects.isNull(paramDTO.getScope()) ?
                FileInfoScopeEnum.PUBLIC.getCode().toString() : paramDTO.getScope().getCode().toString());
        headerMap.put("path", paramDTO.getUploadPath());
        uploadDTO.setHeaders(this.createHeaders(headerMap));
        uploadDTO.setFileKey("file");
        File file = FileUtil.file(paramDTO.getFilePath());
        uploadDTO.setFileName(file.getName());
        uploadDTO.setFilePath(paramDTO.getFilePath());
        Map<String, String> paramMap = MapUtil.newHashMap(1);
        paramMap.put(StringConstants.PLATFORM_ID, this.platformId);
        uploadDTO.setParams(paramMap);
        BaseResponseVO<String> responseVO =
                HttpClientUtil.upload(uploadDTO, new AbstractTypeReference<BaseResponseVO<String>>() {
                });
        if (!MessageEnum.OK.getCode().equals(responseVO.getCode())) {
            throw new AppException(responseVO.getCode(), responseVO.getMsg());
        }
        log.info("FileInfoClient.upload cost {}ms", System.currentTimeMillis() - startTime);
        return responseVO.getData();
    }

    /**
     * 下载文件
     *
     * @param fileId   文件ID
     * @param filePath 下载后的文件绝对路径
     */
    public void download(String fileId, String filePath) {
        Assert.notEmpty(fileId);
        GetDTO getDTO = new GetDTO(this.createDownloadUrl(fileId), MapUtil.newHashMap(), null);
        HttpClientUtil.download(getDTO, filePath);
    }

    /**
     * 获取文件信息
     *
     * @param fileIdList 文件id列表
     * @return List
     */
    public List<FileInfoDetailDTO> listFileInfo(List<String> fileIdList) {
        BaseRequestVO<List<String>> requestVO = new BaseRequestVO<>();
        requestVO.setParam(fileIdList);
        PostDTO postDTO = new PostDTO(this.createFileInfoUrl(), this.createHeaders(null), requestVO, null);
        BaseResponseVO<List<FileInfoDetailDTO>> responseVO = HttpClientUtil.post(postDTO,
                new AbstractTypeReference<BaseResponseVO<List<FileInfoDetailDTO>>>() {
                });
        if (!MessageEnum.OK.getCode().equals(responseVO.getCode())) {
            throw new AppException(responseVO.getCode(), responseVO.getMsg());
        }
        return responseVO.getData();
    }

    /**
     * 构建文件url
     *
     * @param fileIdList 文件ID列表
     * @param seconds 有效时长，空则默认100秒
     * @return map
     */
    public Map<String, String> buildViewUrl(List<String> fileIdList, Integer seconds) {
        if (CollUtil.isEmpty(fileIdList)) {
            return new HashMap<>(0);
        }
        String urlFormat = "{}/jdf/file/view/{}?platformId={}&sign={}&timestamp={}";
        long threadId = Thread.currentThread().getId();
        int size = this.viewUrlList.size();
        int offset = Objects.isNull(seconds) ? NumberConstant.ONE_HUNDRED : seconds;
        Date offsetDate = DateUtil.offsetSecond(new Date(), offset);
        Long timestamp = offsetDate.getTime();
        Digester digester = SecureUtil.sha1();
        digester.setSalt(timestamp.toString().getBytes(StandardCharsets.UTF_8));
        Map<String, String> map = new HashMap<>(size);
        String fileId;
        String url;
        int index;
        for (int i = 0; i < fileIdList.size(); i++) {
            fileId = fileIdList.get(i);
            // 确保访问的节点能散列分布
            if (i < size) {
                index = (int) (i + threadId + NINETY_NINE) % size;
            } else {
                index = i % size;
            }
            String sign = digester.digestHex(this.secret + fileId);
            url = CharSequenceUtil.format(urlFormat, this.viewUrlList.get(index), fileId, this.platformId, sign, timestamp);
            map.put(fileId, url);
        }
        return map;
    }

    private String createTrustUrl() {
        long threadId = Thread.currentThread().getId();
        int size = this.ipList.size();
        int index = (int) (threadId + NINETY_NINE) % size;
        return this.protocol + HTTP_SUFFIX + ipList.get(index) + "/jdf/api/file/trustFile";
    }

    private String createUploadUrl() {
        long threadId = Thread.currentThread().getId();
        int size = this.ipList.size();
        int index = (int) (threadId + NINETY_NINE) % size;
        return this.protocol + HTTP_SUFFIX + ipList.get(index) + "/jdf/api/file/upload";
    }

    private String createDownloadUrl(String fileId) {
        Map<String, String> map = this.buildViewUrl(CollUtil.newArrayList(fileId), null);
        String url = map.get(fileId);
        String baseUrl = this.viewUrlList.stream()
                .filter(f -> CharSequenceUtil.startWith(url, f)).findAny().orElse(null);
        String path = Objects.isNull(baseUrl) ?
                url : CharSequenceUtil.subAfter(url, baseUrl, false);
        long threadId = Thread.currentThread().getId();
        int size = this.ipList.size();
        int index = (int) (threadId + NINETY_NINE) % size;
        return this.protocol + HTTP_SUFFIX + ipList.get(index) + path;
    }

    private String createFileInfoUrl() {
        long threadId = Thread.currentThread().getId();
        int size = this.ipList.size();
        int index = (int) (threadId + NINETY_NINE) % size;
        return this.protocol + HTTP_SUFFIX + ipList.get(index) + "/jdf/api/file/listFileInfo";
    }

    private String createSignature() {
        return H_MAC.digestHex(this.secret);
    }

    private Map<String, String> createHeaders(Map<String, String> map) {
        Map<String, String> headers = MapUtil.newHashMap();
        if (MapUtil.isNotEmpty(map)) {
            headers.putAll(map);
        }
        headers.put(StringConstants.PLATFORM_ID, this.platformId);
        headers.put(SIGNATURE, this.createSignature());
        return headers;
    }
}
